<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>testD3-19-key-update.html</title>
        <script type="text/javascript" src="d3.v3.js"></script>
        <h2>实现键联结数据，悬停浮动弹窗</h2>
    <style type="text/css">
    /* 鼠标悬停时变色*/
    rect:hover{
        fill :orange;
    }
    /* 过渡效果*/
    rect{
    -moz-transiton:all 0.3s;
    -o-transiton:all 0.3s;
    -webkit-transition:all 0.3s;
    transition:all 0.3s
    }
    /*(2)给提示条加上样式*/
    #tooltip {
        position: absolute;
        width: 200px;
        height: auto;
        padding: 10px;
        background-color: white;
        -webkit-border-radius: 10px;
        -moz-border-radius: 10px;
        border-radius: 10px;
        -webkit-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
        -moz-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
        box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
        pointer-events: none;
    }

    #tooltip.hidden {
        display: none;
    }

    #tooltip p {
        margin: 0;
        font-family: sans-serif;
        font-size: 16px;
        line-height: 20px;
    }

    </style>
    </head>
    <body>
    <button>单击更新</button>
    <br>
    <p id="remove" class="click">单击删除</p><div id="add" class="click">单击添加</div>
    <br>

    <!-- (1)创建div提示层 -->
    <div id="tooltip" class="hildden">
        <p><strong>提示：</strong></p>
        <p><span id="value">100</span>%</p>
    </div>
    <br>
        <script type="text/javascript">

            //键值对数据集
            var dataset = [ {
                key : 0,
                value : 5
            }, {
                key : 1,
                value : 10
            }, {
                key : 2,
                value : 13
            }, {
                key : 3,
                value : 19
            }, {
                key : 4,
                value : 21
            }, {
                key : 5,
                value : 25
            }, {
                key : 6,
                value : 22
            }, {
                key : 7,
                value : 18
            }, {
                key : 8,
                value : 15
            }, {
                key : 9,
                value : 13
            }, {
                key : 10,
                value : 11
            }, {
                key : 11,
                value : 12
            }, {
                key : 12,
                value : 15
            }, {
                key : 13,
                value : 20
            }, {
                key : 14,
                value : 18
            }, {
                key : 15,
                value : 17
            }, {
                key : 16,
                value : 16
            }, {
                key : 17,
                value : 18
            }, {
                key : 18,
                value : 23
            }, {
                key : 19,
                value : 25
            } ];

            //设置SVG的高宽
            var w = 600;
            var h = 250;
            var barPadding = 1;

            //定义序数比例尺
            var xScale = d3.scale.ordinal()//序数比例尺
                .domain(d3.range(dataset.length))
                .rangeRoundBands([ 0, w ], 0.05);

            // 更新数据引用，包含下面所有关于要使用到d.value的地方
            var yScale = d3.scale.linear()//y仍然是线性比例尺
                .domain([ 0, d3.max(dataset, function(d) {return d.value;}) ])
                .range([ 0, h ]);

            // 定义键函数(简洁)，以备数据绑定到元素的时候使用
            //把所有.data(dataset)改成.data(dataset,key)
            var key = function(d) {
                return d.key;
            };

            //值函数
            var value = function(d) {
                return d.value;
            };

            //条排序函数
            var sortOrders = false;
            var sortBars = function() {
                sortOrders = !sortOrders;//(3)每点击一次排序方向改变
                svg.selectAll("rect")
                    .sort(function(a, b) {
                        if (sortOrders) {
                            //对数据集升序排序
                            return d3.ascending(a.value, b.value);//这个地方注意是键值对所以要加上值的引用b.value
                        } else {
                            //对数据集降序排序
                            return d3.descending(a.value, b.value);
                        }
                    })
                    .transition()
                    .duration(1000)
                    .attr("x", function(d, i) {//对排序之后的横坐标重排
                        return xScale(i);
                    });

                svg.selectAll("text")
                    .sort(function(a, b) {
                        if (sortOrders) {
                            //对数据集升序排序
                            return d3.ascending(a.value, b.value);//这个地方注意是键值对所以要加上值的引用b.value
                        } else {
                            //对数据集降序排序
                            return d3.descending(a.value, b.value);
                        }
                    })
                    .transition()
                    .duration(1000)
                    .attr("x", function(d, i) {//对排序之后的横坐标重排
                        return xScale(i)+ xScale.rangeBand() / 2;
                    });
            };

            d3.select("#tooltip").classed("hidden", true);

            //创建SVG元素
            var svg = d3.select("body")//选中DOM中的目标元素
                .append("svg")//为目标元素附加上一个svg子元素
                .attr("width", w)//设置这个svg的宽
                .attr("height", h);//设置这个svg的高

            //为SVG添加条形
            svg.selectAll("rect")//选中空元素，表示即将创建这样的元素
                .data(dataset, key)//对此后的方法都执行dataset.length遍
                .enter()//数据元素值比前面选中的DOM元素多就创建一个新的DOM元素
                .append("rect")//取得enter的占位元素，并把rect追加到对应的DOM中
                .attr("x", function(d, i) {//设置横坐标，从0开始每次右移元素宽那么长(w / dataset.length)
                    //return i * (w / dataset.length);
                    return xScale(i);//这里使用序数比例尺，自己去找刚才划分好的档位
                }).attr("y", function(d) {//设置纵坐标，纵坐标正方向是从上往下的，所以条有多长就要设置起点是相对于h再向上移动条长
                    return h - yScale(d.value);
                })
                //.attr("width", w / dataset.length - barPadding)//设置元素宽，留出间隙宽barPadding。
                .attr("width", xScale.rangeBand())//这里xScale比例尺已经设置间距了所以直接用
                .attr("height", function(d) {
                    return yScale(d.value);
                }).attr("fill", function(d) {//设置RGB颜色与数值的关系
                    return "rgb(0, 0, " + (d.value * 10) + ")";
                })
                //点击排序
                .on("click", function() {
                    sortBars();
                })
                //(3)更新提示条的值和位置
                .on("mouseover",function(d) {
                    //取得提示显示的位置
                    var xPosition = parseFloat(d3.select(this).attr("x")) + xScale.rangeBand() / 2;
                    var yPosition = parseFloat(d3.select(this).attr("y")) / 2 + h / 2;
                    d3.select("#tooltip")
                        .style("left", xPosition + "px")
                        .style("top", yPosition + "px")
                        .select("#value")
                        .text(d.value);
                    d3.select("#tooltip").classed("hidden", false);
                })
                //移除提示条
                .on("mouseout", function() {
                    //(4)添加隐藏类
                    d3.select("#tooltip").classed("hidden", true);//ID 选择的语法："#tooltip"
                });

            //为条加上数值
            svg.selectAll("text").data(dataset, key).enter().append("text")
                .text(function(d) {
                    return d.value;
                })
                .attr("text-anchor", "middle").attr("x", function(d, i) {
                    return xScale(i) + xScale.rangeBand() / 2;
                })
                .attr("y", function(d) {
                    return h - yScale(d.value) + 14;
                })
                .attr("font-family", "sans-serif")
                .attr("font-size",function(d) {
                    return xScale.rangeBand() / 2;
                })
                .attr("fill", "white");

            //删除一条、添加一条
            d3.selectAll(".click")
              .on("click",function() {
                //根据ID确定点击的是哪个标签
                var paragraphID = d3.select(this).attr("id");
                //添加删除组合起来
                if (paragraphID == "add") {
                    //数据集最后添加数值
                    var maxValue = 75;
                    var newNumber = Math.floor(Math.random() * maxValue);//0-24的整数

                    //根据最后一个key添加一个值
                    var lastKeyValue = dataset[dataset.length - 1].key;
                    dataset.push({
                        key : lastKeyValue + 1,
                        value : newNumber
                    });

                    //更新X轴比例尺
                    xScale.domain(d3.range(dataset.length));
                    //选择所有条
                    var bars = svg.selectAll("rect").data(dataset, key); //绑定数据到元素集，返回更新的元素集

                    var texts = svg.selectAll("text").data(dataset, key);
                    //添加条形元素到最右边
                    bars.enter().append("rect").attr("x", w);//在SVG最右边，不可见

                    texts.enter().append("text");

                    //更新新矩形到可见范围内
                    //并在这个时候根据数据集为每个条设置对应的属性
                    bars.transition()
                        .duration(500)
                        .attr("x",function(d, i) {
                            return xScale(i);
                        })//每个X对应到它相应的档位上
                        .attr("y", function(d) {
                            return h - yScale(d.value);
                        }).attr("width", xScale.rangeBand())//这里xScale比例尺已经设置间距了所以直接用
                        .attr("height", function(d) {
                            return yScale(d.value);
                        }).attr("fill",function(d) {//设置RGB颜色与数值的关系
                            return "rgb(0, 0, " + (d.value * 10) + ")";
                        });

                    texts.transition()
                        .text(function(d) {
                            return d.value;
                        })
                        .attr("text-anchor", "middle")
                        .attr("x",function(d, i) {
                            return xScale(i)+ xScale.rangeBand()/ 2;
                        })
                        .attr("y",function(d) {return h- yScale(d.value)+ 14;})
                        .attr("font-family", "sans-serif")
                        .attr("font-size", "12px")
                        .attr("fill", "red");
                } else {
                    //删除的操作
                    //选择所有条
                    dataset.shift();
                    //更新X轴比例尺
                    xScale.domain(d3.range(dataset.length));

                    var bars = svg.selectAll("rect").data(dataset, key);
                    var texts = svg.selectAll("text").data(dataset, key);
                    //从左侧退出
                    bars.exit().transition().duration(500)
                        .attr("x", -xScale.rangeBand())//w-xScale.rangeBand()间隙宽其实其他负数也行
                        .remove();
                    //从左侧退出
                    texts.exit().transition().duration(500)
                        .attr("x", -xScale.rangeBand())//w-xScale.rangeBand()间隙宽其实其他负数也行
                        .remove();
                }

            });

            // 更新条形数长短的代码,需要一个button标签配合
            //特别注意：这里选中的元素必须在d3选择器之前，或许要先加载完了元素才能被选中
            d3.select("button").on("click",function() {
                // 新数据集,随机数组
                var numValues = dataset.length;
                dataset = [];
                var maxValue = 75;
                var newNumber;
                for ( var i = 0; i < numValues; i++) {
                    newNumber = Math.floor(Math.random() * maxValue);//0-24的整数
                    //根据i添加一个值
                    dataset.push({
                        key : i,
                        value : newNumber
                    });
                }
                // 更新比例尺，免使纵坐标超出范围
                yScale.domain([ 0, d3.max(dataset, value) ]);//只要更新定义域就行了，映射到的值域不变
                //更新所有的矩形
                svg.selectAll("rect").data(dataset, key).transition()// 加上过渡动画
                .delay(function(d, i) {
                    return i / dataset.length * 1000;
                })//指定过度什么时间开始，可以用函数控制每一条的动画时间，这样就可得到钢琴版的效果
                .duration(2000)// 加上动画的持续时间，以毫秒计算
                .ease("linear")// 缓动函数：有circle（加速）elastic（伸缩），linear（匀速），bounce（弹跳）
                .attr("y", function(d) {
                    return h - yScale(d.value);
                }).attr("height", function(d) {
                    return yScale(d.value);
                });

            // 更新条上的数值
            svg.selectAll("text")
                .data(dataset, key)
                .text(function(d) {
                    return d.value;
                })
                .attr("text-anchor", "middle")
                .attr("x",function(d, i) {
                    return xScale(i) + xScale.rangeBand() / 2;
                })
                .attr("y", function(d) {
                    return h - yScale(d.value) + 14;
                })
                .attr("font-family", "sans-serif").attr("font-size","12px")
                .attr("fill", "red");
            });
        </script>
    </body>
</html>